/*
 * cpu.c
 *
 *  Created on: Mar 4, 2012
 *      Author: gg
 */
#ifdef __unix__
# include <unistd.h>
#elif defined _WIN32
# include <windows.h>
#define sleep(x) Sleep(1000 * x)
#endif


#include<stdio.h>
#include<unistd.h>
#include "cpu.h"
#include "../IO Devices/io_device.h"
#include "../Processes/process.h"
#include "../Utility/semaphore.h"
#include "interrupt.h"

#include "../Scheduler/scheduler.h"

#include "../Utility/instruction.h"
#include "../Utility/queue.h"

/* 	RUNNING = 0,
 *  RUNNABLE = 1,
 *  BLOCKED= 2,
 */

/*
 * GLOBALS
 */
cpu * my_cpu;
int CONSECUTIVE_INTERRUPT_COUNT = 10;
int NO_OP_BUSY_WAIT_CONSTANT = 10;
/*
 * END GLOBALS
 */


/* Interrupt Controller */

void store_program_data(){
	my_cpu->my_active_process->savepc(my_cpu->my_active_process, my_cpu->PC);
}

void context_switch(){
	//store current process PCB info
	store_program_data();
	//switch process
	process * new_proc = my_cpu->my_scheduler->get_next_process(my_cpu->my_scheduler);
	//TODO: If All procs are blocked - spin on Check Interrupts until something Unblocks.
	my_cpu->my_active_process = new_proc;
	printf("Switched to new Process: New process id is: %d\n",  new_proc->pcb.process_id);

	//reset pc
	my_cpu->PC = new_proc->getpc(new_proc);

	//PCB info saved
}

void handleInterrupt(interrupt * current_interrupt){
	switch(current_interrupt->type){
	process * proc = (process * ) FrontAndDequeue(current_interrupt->blocked_q);
		case KEYBOARD:
			//printf("Keyboard\n");
			unblock_process(my_cpu->my_scheduler, proc->pcb.process_id);
			// Notify Scheduler to Unblock this Process
			// Resuming working on active process...
			break;
		case IODEVICE:
			//printf("IO DEVICE\n");
			unblock_process(my_cpu->my_scheduler, proc->pcb.process_id);
			// Notifiy the Scheduler to Unblock this Process
			// Resume working on active process...
			break;
		case SYSTEMTIMER:
			//printf("System Timer\n");
			context_switch();
			break;
		default:
			//printf("***ERROR CPU has no idea what kind of Interrupt this was...");
			break;
	}
}

int checkInterrupts(Queue interrupt_queue){
	int retval = 0;
	if(!IsEmpty(interrupt_queue)){
		while(!IsEmpty(interrupt_queue)){
			interrupt* intr = (interrupt*)FrontAndDequeue(interrupt_queue);
			printf("Handling Interrupts from %s\n\n", intr->process_name);
			handleInterrupt(intr);
			retval += 1;
			break;
		}
	}
	return retval;
}


/**
 *
 * Gavin Edit: this code should  replaced by using the Queue, but is still in sys timer
 * Interrupt Type 0=Keyboard, 1=IO Device, 2=System Timer,
 * IO Devices, System Timer and Keyboard will use this to trigger an Interrupt
 */
void enqueue_interrupt(cpu* cpu_ptr, interrupt * intr){
	//add an interrupt to the interrupt queue
	Enqueue(intr, cpu_ptr->my_interrupt_queue);
	//printf("CPU: Adding %s Interrupt to the Queue!\n", intr->process_name);
}


/*  END Interrupt Controller */


/*
 * This code handles instructions
 */

void preform_no_op(){
	// preform lame instruction
    int i = 0;
	while(i < NO_OP_BUSY_WAIT_CONSTANT){
		int total = 42 * 42;
		i++;
	}
}

void preform_dec_memory(int address){
	//todo: keep track of blocked process and unblock on memory dec
	int id = my_cpu->my_active_process->pcb.process_id;
	char* name = my_cpu->my_active_process->name;

	if(my_cpu->my_memory->check_location(my_cpu->my_memory, address) >= 1){
		my_cpu->my_memory->down(my_cpu->my_memory, address);
		printf("Process %d (%s) decremented memory location %d.\n", id, name, address);
	}
	else{
		printf("Process %d (%s) attempted to decrement memory location %d.\n", id, name, address );
		my_cpu->my_active_process->pcb.waiting_on = address;
		block_running_process(my_cpu->my_scheduler, id);
		context_switch();
	}
}

void preform_inc_memory(int address){
	int id = my_cpu->my_active_process->pcb.process_id;
	char* name = my_cpu->my_active_process->name;
	my_cpu->my_memory->up(my_cpu->my_memory, address);
	printf("Process %d (%s) incremented memory location %d.\n", id, name, address);
	unblock_memory(my_cpu->my_scheduler, address);
}

void preform_process_io_wait(instruction* instr){
	process * proc = my_cpu->my_active_process;
	int id = proc->pcb.process_id;
	char* name = proc->name;
	int did = instr->operation;
	char* dname;
	switch(instr->address){
	case KEYBOARD:
		dname = "Keyboard";
		break;
	case IODEVICE:
		dname = "Auxiliary";
		break;
	}
	printf("Process %d (%s) made a system call to device %d (%s).\n", id, name, did, dname);
	// store & block process
	store_program_data();
	// Add interrupt to Auxiliary's blocked queue
	Enqueue(proc, my_cpu->my_aux_io->my_blocked_q);
	// Block Process
	block_running_process(my_cpu->my_scheduler, id);
	// Context Switch
	context_switch();
}

/*
 * Given an instruction, routes it to the handler based on OPCODE with the appropriate value
 */
int execute_instruction(instruction * instruction){
	enum OPCODE operation = instruction->operation;
	int retval = 0;
	switch(operation){
	case NO_OP:
		preform_no_op();
		break;
	case DEC_MEMORY:
		preform_dec_memory (instruction->address);
		retval = 1;
		break;
	case INC_MEMORY:
		preform_inc_memory(instruction->address);
		retval = 1;
		break;
	case INPUT_WAIT:
		preform_process_io_wait(instruction);
		retval = 1;
		break;
	}
	return retval;
}

/*
 * end of instruction handlers
 */


int step(cpu* the_cpu){
	//get current instruction & execute it
	//printf("CPU: IP: %d\n", my_cpu->PC);
	int retval = 0;
	//if active process is not null: if we're not blocked by some input...
	if(the_cpu->my_active_process != NULL){
		instruction * current_instruction = the_cpu->my_active_process->getinstruction(the_cpu->my_active_process, the_cpu->PC );
		//printdebug(my_cpu->my_active_process);
		retval += execute_instruction(current_instruction);
		the_cpu->PC = ((the_cpu->PC + 1) % (the_cpu->my_active_process->no_steps - 1));
	}
	//if the active process is null, we STILL WANT TO CHECK FOR INTERRUPTS!
	//check for interrupts.
	retval += checkInterrupts(the_cpu->my_interrupt_queue);
	sleep(1);
	return retval;
}

/*
 * CPU Constructor...
 */
void cpu_init(cpu* c, scheduler * task_scheduler, memory * ram, process** processes, int process_count, int request_count,
		Queue the_interrupt_q, io_device* the_keyboard, io_device* the_aux_io){

	c->my_memory = ram;
	c->my_scheduler = task_scheduler;
	c->my_active_process = c->my_scheduler->get_next_process(c->my_scheduler);
	c->PC = c->my_active_process->getpc(c->my_active_process);
	c->my_interrupt_queue = CreateQueue(CONSECUTIVE_INTERRUPT_COUNT);

	//these should already be initialized ...
	c->my_keyboard = the_keyboard;
	c->my_aux_io = the_aux_io;

	// Add the "Semaphore IPC" structure
	semaphore_t* semaphores;
	semaphores = (semaphore_t*)malloc(sizeof(semaphore_t) * (process_count / 2));  // Worst Case, EVERY process is a Producer / Consumer
	int i;
	for(i = 0; i < process_count / 2; i++){
		semaphore_t sem;
		semaphore_init(&sem, request_count);
		semaphores[i] = sem;
	}
	c->my_semaphores = semaphores;
	c->my_interrupt_queue = the_interrupt_q;

}

void run(cpu* cpu){
	// Begin Executing "MAIN" for this CPU Thread.
	my_cpu = cpu;
	while(1){
		if (step(my_cpu)){
			printOutput(cpu->my_scheduler);
		}
	}
}

